#pragma once 

#include "sock.hpp"
#include <sys/select.h>
#define BACKLOG 5
#define NUM sizeof(fd_set)*8
#define DFL_FD -1

namespace ns_select{

    class SelectServer{
        private:
            int listen_sock;
            int port;
        public:
            SelectServer(int _port):port(_port)
            {}
            
            //初始化服务器
            void InitServer(){
                //创建监听套接字
                listen_sock = ns_sock::Sock::Socket();
                //绑定端口号
                ns_sock::Sock::Bind(listen_sock,port);
                //监听
                ns_sock::Sock::Listen(listen_sock,BACKLOG);
            }

            //启动服务器
            void Run(){
                std::cout<< "Server start ..." <<std::endl;

                //读文件描述符集
                fd_set rfds;
                //临时保存文件描述符信息
                int fd_array[NUM] = {0};
                //初始化数组中所有的FD为DFL_FD(无效)
                ClearArray(fd_array, NUM, DFL_FD);
                //把listen_sock 提前放到数组的第一个元素
                fd_array[0] = listen_sock;

                while(true){
                    /////////////////////////////////////////////////////
                    //每次进入循环，都把有效的fd设置进rfds
                    //
                    //时间也是输入输出型参数，每次进入循环都对时间进行设置
                    //struct timeval timeout = {1,0}; //每隔5s timeout
                    //
                    int max_fd = DFL_FD;
                    //清空rfds
                    FD_ZERO(&rfds);

                    for(int i = 0; i < NUM; ++i){
                        if(fd_array[i] == DFL_FD){
                            //如果文件描述符无效，继续遍历
                            continue;
                        }
                        //添加有效的文件描述符
                        FD_SET(fd_array[i], &rfds);
                        
                        //更新文件描述符最大值
                        if(max_fd < fd_array[i]){
                            max_fd = fd_array[i];
                        } 
                    }
                    //
                    //rfds设置完毕
                    ///////////////////////////////////////////////////
                    //1 阻塞等待 不设置timeout
                    //2 非阻塞轮询 timeval = {0,0} 
                    //3 阻塞+轮询 timeval = {5,0}  5s之内，阻塞等待 5s之后，select返回
                    switch(select(max_fd + 1, &rfds, nullptr, nullptr ,/*&timeout*/nullptr)){
                        case 0:
                            std::cout << "timeout!!!"  <<std::endl;
                            break;
                        case -1:
                            std::cerr << "select error!" <<std::endl;
                            break;
                        default:
                            //处理事件
                            HanderEnent(rfds,fd_array,NUM);
                            break;
                    }//end switch       
                }//end for
            }

            ~SelectServer(){}

            void HanderEnent(const fd_set &rfds,int fd_array[],int num){
                //判定fd是否在rfds中
                for(int i=0; i < num; ++i){
                    //过滤无效fd
                    if(fd_array[i] == DFL_FD){
                        continue;
                    }
                    
                    //判定有效fd是否就绪
                    if(fd_array[i] == listen_sock && FD_ISSET(fd_array[i], &rfds)){
                        //连接事件就绪
                        //获取连接
                        struct sockaddr_in peer;
                        socklen_t len = sizeof(peer);

                        //这里进行accept时，由于连接事件已经就绪，所以accept不会阻塞
                        int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
                        if(sock < 0){
                            std::cerr << "accept error" <<std::endl;
                            continue;
                        }
                        //获取到一个新连接
                        //打印客户端信息
                        uint16_t peer_port = htons(peer.sin_port);
                        std::string peer_ip = inet_ntoa(peer.sin_addr);

                        std::cout<< "有一个新连接 " << "ip :[" << peer_ip << "]  port: [" <<  peer_port << "]" <<std::endl;
                        

                        //把获取上来的sock添加到fd_array数组
                        if(!AddFdToArray(fd_array, num, sock)){
                            //添加失败
                            close(sock);
                            std::cerr << "服务器连接已满!!!" <<std::endl;
                        }
                        
                    }
                    else{
                        if(FD_ISSET(fd_array[i], &rfds)){
                            //处理读事件就绪情况
                            char buffer[1024];
                            ssize_t size = recv(fd_array[i], buffer, sizeof(buffer)-1, 0);
                            if(size > 0){
                                buffer[size] = 0;
                                std::cout<< "echo# " << buffer ;
                            }
                            else if(size == 0){
                                std::cout<< "客户端断开连接" <<std::endl;
                                close(fd_array[i]);
                                //清除fd_array中的文件描述符
                                fd_array[i] = DFL_FD;
                            }
                            else{
                                std::cerr << "读取出错!!!" <<std::endl;
                                close(fd_array[i]);
                                //清除fd_array中的文件描述符
                                fd_array[i] = DFL_FD;
                            }
                        }
                        else{
                            //其他事件就绪情况
                        }
                    }
                }
            }
        private:
            //初始化数组中的所有值为无效
            void ClearArray(int fd_array[], int num, int default_fd){
                for(int i = 0; i < num ;++i){
                    fd_array[i] = default_fd;
                }
            }
            //添加fd到fd_array数组
            bool AddFdToArray(int fd_array[], int num, int sock){
                for(int i = 0; i < num; ++i){
                    if(fd_array[i] == DFL_FD){
                        fd_array[i] = sock;
                        //添加成功
                        return true;
                    }
                }//end for
                //循环完毕，没有添加成功，说明数组内的空间已经被使用完毕
                return false;
            }
    };
}
